/*
 * vim: ts=4 sw=4 fdm=marker noet tw=78 ai
 * %%LICENSE%%
 */
#include <sys/cdefs.h>
#ifdef __FBSDID
__FBSDID("$Coar$");
#endif
#include <sys/queue.h>

#include <assert.h>
#include <stdlib.h>
#include <string.h>

#include <sysexits.h>
#include <err.h>

#include <ldap.h>
#include <ldap_schema.h>

#include "extern.h"
#include "search.h"
#include "attributes.h"

static int lde_add_attribute_name(runtime_t *, attr_list_t *, char *);
static attr_entry_t *lde_attribute_new(char *, char *, size_t);

/*
 * Add an attribute value
 * To support multi valued attributes, we do add duplicates. However, if the
 * name is already available in the attribute list /and/ it's value is NULL,
 * then we set that attribute's value.
 * NOTE:
 * Caller is responsible for initialization of the attribute list, as it
 * allows caller to also clear it when needed.
 */
int
lde_add_attribute_value(runtime_t *ctx, attr_list_t *lst, char *name,
		struct berval *value)
{
	int retval = LDAP_SUCCESS;
	attr_entry_t *new;

	assert(ctx != NULL);
	assert(lst != NULL);
	TRACEIN(ctx, __func__, "Adding value for %s: %s", name, value->bv_val);

	new = lde_find_attribute(ctx, lst, name);
	if( new == NULL || new->value != NULL )
	{
		LOG(ctx, 2, "Not found or empty value");
		new = lde_attribute_new(name, value->bv_val, (size_t)value->bv_len);
		if( new == NULL )
		{
			retval = LDAP_OPERATIONS_ERROR;
			goto out;
		}
		LOG(ctx, 2, "Inserting %s: %s (%zu)", new->name, new->value,
				new->value_len);
		STAILQ_INSERT_TAIL(LDE_LIST_HEAD(lst), new, next);
	}
	else
	{
		if( (new->value = malloc(value->bv_len)) == NULL )
		{
			retval = LDAP_OPERATIONS_ERROR;
			goto out;
		}
		(void)memcpy(new->value, value->bv_val, value->bv_len);
		new->value_len = value->bv_len;
	}

out:
	TRACEOUT(ctx, __func__, "Returning 0x%02x", retval);
	return (retval);
}

attr_entry_t *
lde_find_attribute(runtime_t *ctx, attr_list_t *lst, char *name)
{
	attr_entry_t *cur, *retval = NULL;

	assert(ctx != NULL);
	assert(name != NULL);
	TRACEIN(ctx, __func__, "Looking for %s", name);

	if( lst == NULL )
		goto out;

	STAILQ_FOREACH(cur, LDE_LIST_HEAD(lst), next)
	{
		if( strcasecmp(cur->name, name) == 0 )
		{
			retval = cur;
			break;
		}
	}

out:
	TRACEOUT(ctx, __func__, "Returning %p", retval);
	return(retval);
}

int
lde_fetch_class_attributes(runtime_t *ctx, attr_list_t *lst, char *oc_name)
{
	int retval; // Return value
	int i; // iterator
	class_entry_t *cur; // current object class

	assert(ctx != NULL);
	assert(oc_name != NULL);
	TRACEIN(ctx, __func__, "Looking for class: %s", oc_name);

	SLIST_FOREACH(cur, LDE_LIST_HEAD(ctx->ocl), next)
	{
		if( strcasecmp(cur->name, oc_name) == 0 )
		{
			/*
			 * We skip abstract classes:
			 * - They should be implemented, so we will always encounter one
			 *   that does the implementation.
			 * - And as such we might as well skip the duplicate
			 */
			if( cur->lo->oc_kind == LDAP_SCHEMA_ABSTRACT )
				break;

			/* Required attributes */
			if( cur->lo->oc_at_oids_must != NULL )
			{
				for( i=0; cur->lo->oc_at_oids_must[i] != NULL; i++ )
				{
					retval = lde_add_attribute_name(ctx, lst,
							cur->lo->oc_at_oids_must[i]);
					if( retval != LDAP_SUCCESS )
						goto out;
				}
			}
			/* Optional attributes */
			if( cur->lo->oc_at_oids_may != NULL )
			{
				for( i=0; cur->lo->oc_at_oids_may[i] != NULL; i++ )
				{
					retval = lde_add_attribute_name(ctx, lst,
							cur->lo->oc_at_oids_may[i]);
					if( retval != LDAP_SUCCESS )
						goto out;
				}
			}
			/* Recurse superior objects */
			if( cur->lo->oc_sup_oids != NULL )
			{
				for( i=0; cur->lo->oc_sup_oids[i] != NULL; i++ )
				{
					retval = lde_fetch_class_attributes(ctx, lst,
							cur->lo->oc_sup_oids[i]);
					if( retval != LDAP_SUCCESS )
						goto out;
				}
			}
		}
	}

out:
	TRACEOUT(ctx, __func__, "Returning %02x", retval);
	return (retval);
}

/*
 * Add an attribute's name.
 * We do not add:
 * - Duplicates
 * - the "objectClass" attribute
 */
static int
lde_add_attribute_name(runtime_t *ctx, attr_list_t *lst, char *attr_name)
{
	int retval = LDAP_SUCCESS; // Return value
	attr_entry_t *cur; // current attribute
	attr_entry_t *new; // new attribute

	assert(ctx != NULL);
	assert(attr_name != NULL);
	assert(lst != NULL);
	TRACEIN(ctx, __func__, "Adding attribute %s", attr_name);

	// Do not add the "objectClass" as an attribute
	if( ATTR_IS_OC(attr_name) )
		goto out;

	STAILQ_FOREACH(cur, LDE_LIST_HEAD(lst), next)
	{
		if( STRCASE_EQ(cur->name, attr_name) )
			goto out;
	}
	if( (new = malloc(sizeof(attr_entry_t))) == NULL )
	{
		retval = LDAP_OPERATIONS_ERROR;
		goto out;
	}
	memset(new, 0, sizeof(attr_entry_t));

	new->name = strdup(attr_name);
	new->name_len = strlen(attr_name);

	LOG(ctx, 2, "Adding attribute %s", new->name);
	STAILQ_INSERT_TAIL(LDE_LIST_HEAD(lst), new, next);
	lst->cnt++;

out:
	TRACEOUT(ctx, __func__, "Returning %02x", retval);
	return (retval);
}

static attr_entry_t *
lde_attribute_new(char *name, char *value, size_t val_len)
{
	attr_entry_t *retval;

	assert(name != NULL);
	if( (retval = malloc(sizeof(attr_entry_t))) != NULL )
	{
		memset(retval, 0, sizeof(attr_entry_t));
		retval->name = strdup(name);
		retval->name_len = strlen(name);
		if( val_len )
		{
			if( (retval->value = malloc(val_len+1)) == NULL )
			{
				free(retval->name);
				free(retval);
				retval = NULL;
			}
			else
			{
				(void)memcpy(retval->value, value, val_len);
				retval->value[val_len] = '\0';
				retval->value_len = val_len;
			}
		}
	}

	return(retval);
}

void
lde_attribute_list_init(attr_list_t **ptr)
{
	attr_list_t *lst = *ptr;

	if( lst != NULL )
	{
		attr_entry_t *n1, *n2;

		n1 = STAILQ_FIRST(LDE_LIST_HEAD(lst));
		while( n1 != NULL )
		{
			n2 = STAILQ_NEXT(n1, next);
			free(n1->name);
			free(n1->value);
			free(n1);
			n1 =  n2;
		}
		free(lst);
		// Not needed: lst = NULL;
	}
	if( (lst = malloc(sizeof(attr_list_t))) == NULL )
		err(EX_OSERR, "Unable to allocate an attribute list");

	STAILQ_INIT(LDE_LIST_HEAD(lst));
	lst->cnt = 0;
	*ptr = lst;
}

/*
 * Return a list of attributes that are in first but not in second
 */
attr_list_t *
lde_attribute_list_diff(runtime_t *ctx, attr_list_t *first,
		attr_list_t *second)
{
	attr_list_t  *retval = NULL;
	attr_entry_t *cur;

	assert(first != NULL);
	assert(second != NULL);

	if( STAILQ_EMPTY(LDE_LIST_HEAD(first)) )
		return NULL;

	if( STAILQ_EMPTY(LDE_LIST_HEAD(second)) )
		return first;

	lde_attribute_list_init(&retval);
	if( retval == NULL )
		return NULL;

	STAILQ_FOREACH(cur, LDE_LIST_HEAD(first), next)
	{
		if( ATTR_IS_OC(cur->name) )
			continue;
		if( lde_find_attribute(ctx, second, cur->name) == NULL )
		{
			STAILQ_INSERT_TAIL(LDE_LIST_HEAD(retval), cur, next);
			retval->cnt++;
		}
	}

	return (retval);
}
